// HPCPageRank.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <fstream>
#include "Matrix.h"
#include "MatrixOperations.h"
#include "Diagnostic.h"
#include "ResizeArray.h"


using std::string;
using std::fstream;
using std::ifstream;
using std::ios;
using std::istream;
using std::cin;
using std::cout;
using std::time;
using Diagnostic::Stopwatch;
using std::ofstream;

/*
void MultiplyWithOutAMP() {

    int aMatrix[3][2] = {{1, 4}, {2, 5}, {3, 6}};
    int bMatrix[2][3] = {{7, 8, 9}, {10, 11, 12}};
    int product[3][3] = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};

    for (int row = 0; row < 3; row++) {
        for (int col = 0; col < 3; col++) {
            // Multiply the row of A by the column of B to get the row, column of product.
            for (int inner = 0; inner < 2; inner++) {
                product[row][col] += aMatrix[row][inner] * bMatrix[inner][col];
            }
            std::cout << product[row][col] << "  ";
        }
        std::cout << "\n";
    }
}

void MultiplyWithAMP() {
    int aMatrix[] = { 1, 4, 2, 5, 3, 6 };
    int bMatrix[] = { 7, 8, 9, 10, 11, 12 };
    int productMatrix[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };

    array_view<int, 2> a(3, 2, aMatrix);
    array_view<int, 2> b(2, 3, bMatrix);
    array_view<int, 2> product(3, 3, productMatrix);

    parallel_for_each(
        product.extent, 
         [=](index<2> idx) restrict(amp) {
            int row = idx[0];
            int col = idx[1];
            for (int inner = 0; inner < 2; inner++) {
                product[idx] += a(row, inner) * b(inner, col);
            }
        }
    );

    product.synchronize();

    for (int row = 0; row < 3; row++) {
        for (int col = 0; col < 3; col++) {
            //std::cout << productMatrix[row*3 + col] << "  ";
            std::cout << product(row, col) << "  ";
        }
        std::cout << "\n";
    }
}

void MultiplyWithTiling()
{
    // The tile size is 2.
    static const int TS = 2;

    // The raw data.
    int aMatrix[] =       { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };
    int bMatrix[] =       { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8 };
    int productMatrix[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

    // Create the array_view objects.
    array_view<int, 2> a(4, 4, aMatrix);
    array_view<int, 2> b(4, 4, bMatrix);
    array_view<int, 2> product(4, 4, productMatrix);

    // Call parallel_for_each by using  2x2 tiles.
    parallel_for_each(product.extent.tile< TS, TS >(),
        [=] (tiled_index< TS, TS> t_idx) restrict(amp) 
        {
            // Get the location of the thread relative to the tile (row, col) and the entire array_view (rowGlobal, colGlobal).
            int row = t_idx.local[0]; 
            int col = t_idx.local[1];
            int rowGlobal = t_idx.global[0];
            int colGlobal = t_idx.global[1];
            int sum = 0;

            // Given a 4x4 matrix and a 2x2 tile size, this loop executes twice for each thread.
            // For the first tile and the first loop, it copies a into locA and e into locB.
            // For the first tile and the second loop, it copies b into locA and g into locB.
            for (int i = 0; i < 4; i += TS) {
                tile_static int locA[TS][TS];
                tile_static int locB[TS][TS];
                locA[row][col] = a(rowGlobal, col + i);
                locB[row][col] = b(row + i, colGlobal);
                // The threads in the tile all wait here until locA and locB are filled.
                t_idx.barrier.wait();


                // Return the product for the thread. The sum is retained across
                // both iterations of the loop, in effect adding the two products
                // together, for example, a*e.
                for (int k = 0; k < TS; k++) {
                    sum += locA[row][k] * locB[k][col];
                }
                
                // All threads must wait until the sums are calculated. If any threads
                // moved ahead, the values in locA and locB would change.      
                t_idx.barrier.wait();
                // Now go on to the next iteration of the loop.          
            }

            // After both iterations of the loop, copy the sum to the product variable by using the global location.
            product[t_idx.global] = sum;
    });

        // Copy the contents of product back to the productMatrix variable.
        product.synchronize();

        for (int row = 0; row < 4; row++) {
        for (int col = 0; col < 4; col++) {
            // The results are available from both the product and productMatrix variables.
            //std::cout << productMatrix[row*3 + col] << "  ";
            std::cout << product(row, col) << "  ";
        }
        std::cout << "\n";
    }

}
*/


int main(int argc, char* argv[])
{
	/*ResizeArray<float>* ar = new ResizeArray<float>(-1, 3);
	ar->InsertAt(0, 0.);
	ar->Diagnostic();
	ar->Add(1.);
	ar->Diagnostic();
	ar->Add(2.);
	ar->Diagnostic();
	ar->Add(3.);
	ar->Diagnostic();
	ar->Add(4.);
	ar->Diagnostic();
	ar->Add(5.);
	ar->Diagnostic();
	ar->Add(6.);

	ar->Diagnostic();

	for (int i = 0; i < ar->Count; i++)
	{
		cout << (*ar)[i] << "; ";
	}


	return 1;*/


	Stopwatch* chrono = new Stopwatch();
	Stopwatch* chrono_ite = new Stopwatch();
	Stopwatch* total_chrono = new Stopwatch();
	total_chrono->Start();
	string in_name = "p2p-Gnutella30.txt";//"Test.txt";//"p2p-Gnutella30.txt";
	ifstream f_in (in_name);
#if CHUNKED
	string out_name = "res_chunked" + in_name;
#else
	string out_name = "res_simple" + in_name;
#endif
	ofstream f_out (out_name);

	string s;
	uint64 dim;
	
	getline(f_in, s);
	getline(f_in, s);
	f_in >> s >> s >> dim >> s >> s;
	getline(f_in, s);
	getline(f_in, s);
	


	//auto b = new int[13960000];
	//uint64 dim = 10;//36682;//36000;

	float* start = new float[dim];
	float* res = new float[dim];
	uint64* outLink = new uint64[dim];

	chrono->Start();
#if CHUNKED
	ChunkedMatrix* mat = new ChunkedMatrix(dim, dim, dim);
#else
	Matrix* mat = new Matrix(dim, dim, 100);
#endif
	chrono->End();
	cout << "Matrix allocated in " << chrono->GetElapsed() << " seconds.\n";
	f_out << "Matrix allocated in " << chrono->GetElapsed() << " seconds.\n";
	
	chrono->Start();
	for (uint64 i = 0; i < dim; i++)
	{
		start[i] = 1.0f / (float)dim;
		outLink[i] = 0;
	}
	uint64 c = 0;
	chrono->End();
	cout << "Vectors filled in " << chrono->GetElapsed() << " seconds.\n";
	f_out << "Vectors filled in " << chrono->GetElapsed() << " seconds.\n";
	
	chrono->Start();
	while (!f_in.eof())
	{
		uint64 from = 0, to = 0;
		//string s;
		//f_in >> s;
		//f_out << s << ":" << c++ << "\n";
		//if (c > 1)
		//	break;
		f_in >> from >> to;
		getline(f_in, s);
		//c++;
		//if (c % 1000 == 0)
		//	f_out << c << ": " << from << "-" << to << ";\n";
		(*mat)(from, to) = 1;
	}
	chrono->End();
	cout << "Matrix filled in " << chrono->GetElapsed() << " seconds.\n";
	f_out << "Matrix filled in " << chrono->GetElapsed() << " seconds.\n";


	chrono->Start();
	for (int i = 0; i < dim; i++)
	{
		for (int j = 0; j < dim; j++)
		{
			if ((*mat)(i,j) != 0)
				outLink[i] += 1;
		}
	}
	chrono->End();
	cout << "Outlink filled in " << chrono->GetElapsed() << " seconds.\n";
	f_out << "Outlink filled in " << chrono->GetElapsed() << " seconds.\n";

	chrono->Start();
	//for (int i = 0; i < dim; i++)
	//{
	//	if (outLink[i] == 0)
	//	{
	//		for (int j = 0; j < dim; j++)
	//		{
	//			(*mat)(i,j) = 1.0f / (float) dim;
	//		}
	//	}
	//	else
	//	{
	//		for (int j = 0; j < dim; j++)
	//		{
	//			if ((*mat)(i,j) != 0)
	//			{
	//				(*mat)(i,j) = 1.0f / (float) outLink[i];
	//			}
	//		}
	//	}
	//}
	mat->MakeStochastic(outLink);

	chrono->End();
	cout << "Matrix made stocastic in " << chrono->GetElapsed() << " seconds.\n";
	f_out << "Matrix made stocastic in " << chrono->GetElapsed() << " seconds.\n";

	mat->Test();

	chrono->Start();
	delete[] outLink;	
	chrono->End();
	cout << "Outlink memory relased in " << chrono->GetElapsed() << " seconds.\n";
	f_out << "Outlink memory relased in " << chrono->GetElapsed() << " seconds.\n";

	cout << "matrix allocated completed\n";
	f_out << "matrix allocated completed\n";

	/*	for (int i = 0; i < dim; i++)
	{
		for (int j = 0; j < dim; j++)
		{
			(*mat)(j,i) += 1;
		}
	}

	return 0;*/

	float d = 0.85f;
	bool looping = true;
	uint64 k = 0;
	float error = 0;

	chrono_ite->Start();
	while (looping)
	{
		chrono->Start();
#if CHUNKED
		mat->MultiplyByChunks(start, res);
#else
		Multiply(mat, start, res);
#endif
		for (int i = 0; i < dim; i++)
		{
			res[i] = (d * res[i]) + (1.0f - d) / (float) dim;
		}

		error = 0.0;
		for (int i = 0; i < dim; i++)
		{
			error += abs(res[i] - start[i]);
		}
		if (error < 0.000001f)
		{
			looping = false;
		}

		float* tmp  = start;
		start = res;
		res = tmp;
		k++;
		cout << "iteration " << k << ", error: " << error << "\n";
		f_out << "iteration " << k << ", error: " << error << "\n";
		chrono->End();
		cout << "Iteration preformed in " << chrono->GetElapsed() << " seconds.\n";
		f_out << "Iteration preformed in " << chrono->GetElapsed() << " seconds.\n";
	}
	
	chrono_ite->End();
	cout << "All iterations performed in " << chrono_ite->GetElapsed() << " seconds.\n";
	f_out << "All iterations performed in " << chrono_ite->GetElapsed() << " seconds.\n";

	total_chrono->End();
	cout << "Total program performed in " << total_chrono->GetElapsed() << " seconds.\n";
	f_out << "Total program performed in " << total_chrono->GetElapsed() << " seconds.\n";
	cout << "Iteration to Converge: " << k << "\n";
	f_out << "Iteration to Converge: " << k << "\n";

	//for (int i = 0; i < dim; i++)
	//{
	//	cout << start[i] << "; ";
	//}
	//cout << "\n";
	f_out.close();
	return 0;
}

